package com.citycloud.datac.client.token;

import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.Objects;

import org.apache.xmlbeans.impl.util.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;

import com.alibaba.fastjson.JSON;
import com.citycloud.datac.client.dto.DetailedUserInfo;
import com.citycloud.datac.client.exception.ExceptionHandler;
import com.citycloud.datac.client.exception.OAuth2ClientException;
import com.citycloud.datac.client.properties.SecurityConfigurter;
import com.citycloud.datac.client.resoult.ResultObject;

public class RemoteTokenServices implements ResourceServerTokenServices {
	private static Logger log = LoggerFactory.getLogger(RemoteTokenServices.class);
	private RestOperations restTemplate;
	final SecurityConfigurter.SecurityClientConfig clientConfig;
	final SecurityConfigurter.SecurityResourceConfig resourceConfig;

	public RemoteTokenServices(final SecurityConfigurter.SecurityClientConfig clientConfig,
			final SecurityConfigurter.SecurityResourceConfig resourceConfig) {
		this.clientConfig = clientConfig;
		this.resourceConfig = resourceConfig;
		restTemplate = new RestTemplate();
		/*
		 * 在非MVC的Project中使用RestTemplate.会报错 Could not extract response: no suitable
		 * HttpMessageConverter found for response type ***这个异常 简单的说，message
		 * converters是负责Java Object和Http上的JSON/XML互相转换的中介工具。Ref by baeldung.
		 * 如果是在非MVC的Project中，根本就不会产生HttpMessageConverter ，此时使用RestTemplate会报如上所示的错误。
		 */
		MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
		mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON));
		// TODO 还需要再测试下中文的情况，还有在MVC环境中会有什么样的影响
		((RestTemplate) restTemplate).getMessageConverters().add(mappingJackson2HttpMessageConverter);
		((RestTemplate) restTemplate).setErrorHandler(new ExceptionHandler());
	}

	public void setRestTemplate(RestOperations restTemplate) {
		this.restTemplate = restTemplate;
	}

	@Override
	public Map<String, Object> checkToken(String accessToken) {
		if (StringUtils.isEmpty(accessToken)) {
			throw new OAuth2ClientException("token为空,没法check_token");
		}
		String url = resourceConfig.getTokenInfoUri() + "?token=" + accessToken;
		Map<String, Object> resultObject;
		ResponseEntity<Map> responseEntity;
		try {
			responseEntity = this.restTemplate.getForEntity(url, Map.class);
		} catch (ResourceAccessException e) {
			throw new OAuth2ClientException(e.getMessage());
		}
		log.info("check_token结束--------responseEntity:{}", responseEntity);
		resultObject = (Map<String, Object>) responseEntity.getBody();
		if (resultObject.containsKey("error")) {
			log.error("check_token returned error: " + resultObject.get("error") + "error_description:"
					+ resultObject.get("error_description"));
			throw new OAuth2ClientException("error:" + resultObject.get("error") + ",error_description:"
					+ resultObject.get("error_description") + ",token:" + accessToken);
		} else if (resultObject.containsKey("active") && !"true".equals(String.valueOf(resultObject.get("active")))) {
			log.error("check_token returned active attribute: " + resultObject.get("active"));
			throw new OAuth2ClientException("active:" + resultObject.get("active"));
		}
		resultObject.put("exp",
				secondToDate(
						resultObject.containsKey("exp") ? Long.parseLong(String.valueOf(resultObject.get("exp"))) : 0L,
						"yyyy-MM-dd HH:mm:ss"));
		log.info("exp:{}", resultObject.get("exp"));
		return resultObject;
	}

	@Override
	public DetailedUserInfo getUserInfo(String accessToken) {
		if (StringUtils.isEmpty(accessToken)) {
			throw new RuntimeException("token为空,没法获取用户信息");
		}
		String url = clientConfig.getUserGetUserInfo() + "?token=" + accessToken;
		log.info("getUserInfoUrl:{}", url);
		ResponseEntity<Map> responseEntity;
		try {
			responseEntity = this.restTemplate.getForEntity(url, Map.class);
		} catch (ResourceAccessException e) {
			throw new OAuth2ClientException(e.getMessage());
		}
		Map map = responseEntity.getBody();
		if (map.containsKey("error")) {
			log.error("check_token returned error: " + map.get("error") + "error_description:"
					+ map.get("error_description"));
			throw new OAuth2ClientException("error:" + map.get("error") + ",error_description:"
					+ map.get("error_description") + ",token:" + accessToken);
		}
		if (Objects.isNull(map.get("data"))) {
            throw new OAuth2ClientException("用户信息不存在");
        }
		if (!"200".equals(map.get("code"))) {
            throw new OAuth2ClientException((String) map.get("message"));
        }
		DetailedUserInfo user = JSON.parseObject(JSON.toJSONString(map.get("data")), DetailedUserInfo.class);
		log.info("RemoteTokenServices------user:{}", user);
		return user;
	}

	@Override
	public String logout() {
		return clientConfig.getUserLogout();
	}

	@Override
	public ResultObject getToken(String code) {
		String path = clientConfig.getAccessTokenUri();
		HttpHeaders headers = new HttpHeaders();
		headers.set("Authorization",
				this.getAuthorizationHeader(clientConfig.getClientId(), clientConfig.getClientSecret()));

		MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
		formData.add("grant_type", "authorization_code");
		formData.add("code", code);
		if (StringUtils.isEmpty(code)) {
			return ResultObject.getInstance().error("code为空,没法获取token");
		}
		Map<String, Object> map;
		try {
			map = this.postForMap(path, formData, headers);
		} catch (ResourceAccessException e) {
			log.error(e.getMessage());
			return ResultObject.getInstance().error(e.getMessage());
		}
		if (map.containsKey("error")) {
			log.error("check_token returned error: " + map.get("error") + "error_description:"
					+ map.get("error_description"));
			return ResultObject.getInstance()
					.error("error:" + map.get("error") + ",error_description:" + map.get("error_description"));
		}
		log.info("--------------------url:{}---,map:{}", path, Objects.toString(map));
		map.put("expires_in", map.get("expires_in"));
		log.info("final--map:{}", path, Objects.toString(map));
		return ResultObject.getInstance().success(map);

	}

	@Override
	public ResultObject getToken(String userName, String password) {
		String path = clientConfig.getAccessTokenUri();
		HttpHeaders headers = new HttpHeaders();
		headers.set("Authorization",
				this.getAuthorizationHeader(clientConfig.getClientId(), clientConfig.getClientSecret()));

		MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
		formData.add("grant_type", "password");
		formData.add("username", userName);
		formData.add("password", password);
		if (StringUtils.isEmpty(userName) || StringUtils.isEmpty(password)) {
			return ResultObject.getInstance().error("userName或者password为空,没法获取token");
		}
		Map<String, Object> map;
		try {
			map = this.postForMap(path, formData, headers);
		} catch (ResourceAccessException e) {
			log.error(e.getMessage());
			return ResultObject.getInstance().error(e.getMessage());
		}
		if (map.containsKey("error")) {
			log.error("check_token returned error: " + map.get("error") + "error_description:"
					+ map.get("error_description"));
			return ResultObject.getInstance()
					.error("error:" + map.get("error") + ",error_description:" + map.get("error_description"));
		}
		log.info("--------------------url:{}---,map:{}", path, Objects.toString(map));
		return ResultObject.getInstance().success(map);
	}

	private String getAuthorizationHeader(String clientId, String clientSecret) {
		if (clientId == null || clientSecret == null) {
			log.warn(
					"Null Client ID or Client Secret detected. Endpoint that requires authentication will reject request with 401 error.");
		}
		String creds = String.format("%s:%s", clientId, clientSecret);
		try {
			return "Basic " + new String(Base64.encode(creds.getBytes("UTF-8")));
		} catch (UnsupportedEncodingException var5) {
			throw new IllegalStateException("Could not convert String");
		}

	}

	private Map<String, Object> postForMap(String path, MultiValueMap<String, String> formData, HttpHeaders headers) {
		if (headers.getContentType() == null) {
			headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
		}
		Map map = (Map) this.restTemplate
				.exchange(path, HttpMethod.POST, new HttpEntity(formData, headers), Map.class, new Object[0]).getBody();
		return map;
	}

	private String secondToDate(long second, String patten) {
		Calendar calendar = Calendar.getInstance();
		// 转换为毫秒
		calendar.setTimeInMillis(second * 1000);
		Date date = calendar.getTime();
		SimpleDateFormat format = new SimpleDateFormat(patten);
		String dateString = format.format(date);
		return dateString;
	}

}
